#!/usr/bin/python3
#
#    /$$$$$$          /$$$$$$        /$$      /$$
#   /$$__  $$        /$$__  $$      | $$$    /$$$
#  | $$  \ $$       | $$  \__/      | $$$$  /$$$$
#  | $$$$$$$$       |  $$$$$$       | $$ $$/$$ $$
#  | $$__  $$        \____  $$      | $$  $$$| $$
#  | $$  | $$        /$$  \ $$      | $$\  $ | $$
#  | $$  | $$       | $$$$$$/       | $$ \/  | $$
#  |__/  |__/ TTACK \______/ URFACE |__/     |__/ APPER v1.0
#
#  Andreas Georgiou (@superhedgy)
#  Jacob Wilkin     (@jacob_wilkin)
#
# Example:
# $ python3 asm.py -t example.com -ln
#

# Standard Libraries
import argparse
import ipaddress
import json
import os
import signal
import socket
import sys
from datetime import datetime
from time import time, sleep
from urllib import parse

# External Libraries
import colorama
import pymongo
import requests
from colorama import Fore, Style
from netaddr import IPNetwork
from validator_collection import checkers

# ASM Modules
from modules import buckethunter
from modules import dnsdumpster
from modules import hosthunter
from modules import hunterio
from modules import linkedinner
from modules import screencapture
from modules import shodan
from modules import subhunter
from modules import urlscanio
from modules import whois_collector
from modules import censys

# Constants
__author__ = " Andreas Georgiou (@superhedgy)\n\t Jacob Wilkin (@greenwolf)"
__version__ = "v1.2"


# Classes
class TargetIP:
    def __init__(self, addr):
        self.address = addr
        self.hostname = []
        self.ports = []
        self.asn = ""
        self.asn_name = ""
        self.whois = []
        self.server = ""
        self.vulns = []
        self.cidr = ""
        self.location = ""
        self.country = ""


class Target:
    def __init__(self):
        self.primary_domain = ""
        self.subdomains = []
        self.orgName = ""
        self.dnsrecords = []
        self.buckets = []
        self.mx = None
        self.spf = None
        self.dmarc = []
        self.dmarc_status = ""
        self.emails = []
        self.guessed_emails = []
        self.creds = []
        self.hashes = []
        self.breaches = {}
        self.employees = []
        self.pattern = "{f}{last}"  # Default Email Pattern
        self.urls = []
        self.ipv4 = False
        self.resolved_ips = []


class Counter:
    def __init__(self):
        Counter.targets = 0
        Counter.ports = 0
        Counter.hostnames = 0
        Counter.subdomains = 0
        Counter.urls = 0
        Counter.buckets = 0
        Counter.sc = 0
        Counter.employees = 0
        Counter.intel = 0
        Counter.ips = 0
        Counter.vulns = 0
        Counter.emails = 0
        Counter.guessed_emails = 0
        Counter.creds = 0
        Counter.hashes = 0


class MasterSwitch:
    def __init__(self):
        self.shodan = True
        self.hunterio = True
        self.whois_collector = True
        self.subhunter = True
        self.dnsdumpster = True
        self.urlscanio = True
        self.weleakinfo = True
        self.weleakinfo_private = True
        self.screencapture = True
        self.webscraper = True
        self.linkedinner = False
        self.censys = False
        self.expand = False
        self.stealth = False
        self.verbose = False
        self.debug = False


# Print ACII Banner
def showbanner():
    print('''
  /$$$$$$          /$$$$$$        /$$      /$$
 /$$__  $$        /$$__  $$      | $$$    /$$$
| $$  \ $$       | $$  \__/      | $$$$  /$$$$
| $$$$$$$$       |  $$$$$$       | $$ $$/$$ $$
| $$__  $$        \____  $$      | $$  $$$| $$
| $$  | $$        /$$  \ $$      | $$\  $ | $$
| $$  | $$       | $$$$$$/       | $$ \/  | $$
|__/  |__/ TTACK \______/ URFACE |__/     |__/ APPER ''' + __version__)
    print("\nAuthors:" + __author__ + "\n")
    exit


# Initial Checks & Argument Parsing
def init_checks(master_switch, outpath):
    global args
    # Argument Parser
    parser = argparse.ArgumentParser(description='|<------ AttackSurfaceMapper - Help Page ------>|',
                                     epilog="Authors:" + __author__ + "\n\n ",
                                     formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument("-f", "--format", help="Choose between CSV and TXT output file formats.", default="csv")
    parser.add_argument("-o", "--output", help="Sets the path of the output file.", type=str, default=outpath)
    parser.add_argument("-sc", "--screen-capture", help="Capture a screen shot of any associated Web Applications.",
                        action="store_true", default=False)
    parser.add_argument("-sth", "--stealth", help="Passive mode allows reconnaissance using OSINT techniques only.",
                        action="store_true", default=False)
    parser.add_argument("-t", "--target", help="Set a single target IP.")
    parser.add_argument("targets", nargs='?', help="Sets the path of the target IPs file.", type=str, default="")
    parser.add_argument("-V", "--version", help="Displays the current version.", action="store_true", default=False)
    parser.add_argument("-w", "--wordlist", help="Specify a list of subdomains.", type=str,
                        default="resources/bitquark_top100k_sublist.txt")
    parser.add_argument("-sw", "--subwordlist", help="Specify a list of child subdomains.", type=str,
                        default="resources/top1000_sublist.txt")
    parser.add_argument("-e", "--expand", help="Expand the target list recursively.", action="store_true",
                        default=False)
    parser.add_argument("-ln", "--linkedinner", help="Extracts emails and employees details from LinkedIn.",
                        action="store_true", default=False)
    parser.add_argument("-d", "--debug", help="Enables debugging information.",action="store_true",default=False)
    parser.add_argument("-v", "--verbose", help="Verbose output in the terminal window.", action="store_true",
                        default=False)
    args = parser.parse_args()


    if not (args.target or args.targets):
        cprint("error", "Please specify a single target or a list of targets.", 1)
        cprint("info", "Example Usage: python3 asm.py -t superhedgy.com", 1)
        exit()

    if args.version:
        print("HostHunter version", __version__)
        print("Author:", __author__)
        exit()

    if args.target and args.targets:
        cprint("error", "Too many arguments! Either select single target or specify a list of targets.", 1)
        cprint("info", "Example Usage: python3 asm.py -t superhedgy.com", 1)
        exit()
    # Targets Input File
    if args.targets and not os.path.exists(args.targets):
        cprint("error", "Targets file \"" + args.targets + "\" does not exist", 1)
        exit()

    if args.wordlist is None or args.wordlist == "":
        cprint("error", "Wordlist file argument is empty", 1)
        exit()

    if args.subwordlist is None or args.subwordlist == "":
        cprint("error", "Wordlist file argument is empty", 1)
        exit()

    if args.wordlist and not os.path.exists(args.wordlist):
        cprint("error", "Wordlist file \"" + str(args.wordlist) + "\" does not exist", 1)
        exit()

    if args.subwordlist and not os.path.exists(args.subwordlist):
        cprint("error", "SubWordlist file \"" + str(args.subwordlist) + "\" does not exist", 1)
        exit()

    if args.output and os.path.exists(args.output):
        cprint("info", "\n[?] {0} file already exists, would you like to overwrite it?".format(args.output), 1)
        while True:
            answer = input(
                Fore.WHITE + "[" + Fore.RED + Style.BRIGHT + ">" + Style.RESET_ALL + Fore.WHITE + "]" + Fore.WHITE + " Answer with [Y]es or [N]o : ").lower()
            if answer.startswith("n"):
                exit()
            elif answer.startswith("y"):
                break

    print(Style.RESET_ALL)

    amionline()

    if args.expand:
        cprint("info", "\n[!] Expand Mode Enabled, out-of-scope targets might be included.\n", 1)  # Success Msg
        master_switch.expand = True
        sleep(0.5)

    if args.verbose:
        master_switch.verbose = True


    if args.debug:
        master_switch.debug = True

    return args.output


# MongoDB
mongo_client = pymongo.MongoClient('localhost', 27017)
db = mongo_client.asm
store_targets = db.targets

# Checks for an Internet Connection
def amionline():
    try:
        socket.setdefaulttimeout(5)
        socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect(("8.8.8.8", 53))  # Google DNS IPv4
    except:
        cprint("white", "\n" + 82 * "*", 0)
        cprint("error", "No Internet Connection! Ensure that you are online and run ASM again.", 0)
        print(82 * "*" + "\n")
        while 1:
            try:
                socket.setdefaulttimeout(8)
                socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect(("8.8.4.4", 53))  # OpenDNS IPv4
                return True
            except:
                input("[>] Press any key to resume . . .")
    return True

# Resolve Domain Function - Returns a list
def asn_expansion(mswitch, hostx):
    cprint("info", "[i] Searching for ASNs based on: " + hostx.orgName, 1)
    answer3 = input(
        Fore.WHITE + "[" + Fore.RED + Style.BRIGHT + ">" + Style.RESET_ALL + Fore.WHITE + "]" + "[EXPAND-MODE]" + Fore.WHITE + " Enter Company Name: ")
    print(Style.RESET_ALL)
    if answer3 == "":
        asn_query = parse.quote(hostx.orgName)
    else:
        asn_query = parse.quote(answer3)

    user_agent = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'}
    api_endpoint = "https://api.bgpview.io/search?query_term=" + asn_query
    try:
        response = requests.get(api_endpoint, headers=user_agent)
        api = json.loads(response.text)
    except:
        cprint("error", "Attack surface expansion failed.", 1)
        return -1
    asns = []
    prefixes = []

    if response.status_code == 200:
        try:
            for item in api['data']['asns']:
                # print (item)
                if mswitch.verbose is True:
                    print(60 * "*")
                    print("ASN: " + str(item["asn"]))
                    print("ASN Name: " + item.get("name"))
                    print("Description: " + item["description"])
                    print("Location: " + item["country_code"])
                    print(60 * "*")
                asns.append(item["asn"])
        except:
            cprint("error", "Attack surface expansion failed.", 1)
            return -1

        try:
            for item in api['data']['ipv4_prefixes']:

                if mswitch.verbose is True:
                    print(60 * "*")
                    print("CIDR: " + item["ip"] + "/" + str(item["cidr"]))
                    print("Prefix Name: " + item.get("name"))
                    print("Prefix Description: " + item["description"])
                    print("Location: " + item["country_code"])
                    print(60 * "*")

                prefixes.append(item["prefix"])
        except:
            cprint("error", "Attack surface expansion failed while searching for IPv4 prefixes.", 1)
            return -1

        if len(asns) > 0:
            for asn in asns:
                try:
                    response2 = requests.get("https://api.bgpview.io/asn/{0}/prefixes".format(str(asn)),
                                             headers=user_agent)
                    api2 = json.loads(response2.text)
                    # print(response2.text)
                    print(api2["data"]["ipv4_prefixes"])
                    if response2.status_code == 200:
                        try:
                            for item in api2["data"]["ipv4_prefixes"]:
                                # print (item)
                                if mswitch.verbose is True:
                                    cprint("info", "Expanding prefix:" + str(item["prefix"]), 1)
                                prefixes.append(item["prefix"])
                        except:
                            cprint("error", "Attack surface expansion failed.", 1)
                            return -1
                except:
                    return -1

        if len(prefixes) > 0:
            for prefix in prefixes:
                if mswitch.verbose is True:
                    cprint("info", "[i] Adding IPv4 Prefix to the targets list:" + prefix, 1)
                for ip in IPNetwork(prefix):
                    tmp = TargetIP(ip)
                    if ip not in hostx.resolved_ips:
                        hostx.resolved_ips.append(tmp)


# Resolve Domain Function - Returns a list
def resolve_domain(domain):
    try:
        resolve = socket.gethostbyname_ex(domain)
        IP = resolve[2]
        return IP
    except Exception:
        return ""


# validate Funciton - Validates IP
def add_target_domain(list_domain, input_domain, validated_input):
    input_domain = input_domain.replace("\n", "")
    if not input_domain:
        return 0

    t = Target()
    # Valid Input IPv4
    if checkers.is_ipv4(input_domain):
        if ipaddress.ip_address(input_domain).is_private:
            cprint("error", '"' + input_domain + '"' + " is a Private IPv4 address", 1)
            return 0
        else:
            validated_input.append(input_domain)
            return 0
    # Valid Input Domain
    elif checkers.is_domain(input_domain):
        validated_input.append(input_domain)
        t.ipv4 = False
        t.primary_domain = input_domain
        associated_ips = resolve_domain(t.primary_domain)
        # if not associated_ips:
        #     return 0
        if len(associated_ips) > 0:
            for x in associated_ips:
                tmp = TargetIP(x)
                if not ipaddress.ip_address(x).is_private:
                    t.resolved_ips.append(tmp)

        if t.primary_domain in list_domain.keys():
            cprint("info", "[i] Target Not Added - Domain {0} Exists".format(t.primary_domain), 1)
            pass
        else:
            list_domain[t.primary_domain] = t
            cprint("info", "[i] Target [{0}] Added.".format(t.primary_domain), 1)
    else:
        cprint("error", input_domain + " is not a valid IPv4 address or Domain Name", 1)
        return 0


def add_target_ip(target_list, IP):
    validated_input = []  # Dummy list
    tmp = TargetIP(IP)

    # Validate IPv4 Input Address
    if checkers.is_ipv4(IP):
        if ipaddress.ip_address(IP).is_private:
            return 0

        for key in target_list.keys():
            # print("Key: "+key)
            for x in target_list[key].resolved_ips:
                if (IP == x.address):
                    cprint("info", "Target Not Added - Address {0} already exists.".format(IP), 1)
                    return 0

        domain = urlscanio.get_domain(IP)
        if domain:
            add_target_domain(list, domain, validated_input)
            try:
                target_list[domain].resolved_ips.append(tmp)
            except:
                pass
        # Create a List with Unsorted IPs
        # add_target_domain(list,"",validated_input)


# keyloader Function - Load API Keys
def keyloader(keychain, master_switch):
    keyfile = open("keylist.asm", "rt")  # Read keylist.asm File

    for line in keyfile:
        words = line.split()
        try:
            keychain[words[0]] = words[2].replace("\"", "")
        except:
            pass

    print("{0}   HostHunter Module	: [{1}Enabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.GREEN, Fore.WHITE, Style.RESET_ALL))
    master_switch.hosthunter = True

    if args.screen_capture:
        print("{0}   ScreenCapture Module	: [{1}Enabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.GREEN, Fore.WHITE, Style.RESET_ALL))
        master_switch.screencapture = True
    else:
        print("{0}   ScreenCapture Module	: [{1}Disabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.RED, Fore.WHITE, Style.RESET_ALL))
        master_switch.screencapture = False
    print("{0}   DNSdumpster Module	: [{1}Enabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.GREEN + Style.BRIGHT, Fore.WHITE, Style.RESET_ALL))
    print("{0}   URLScanIO Module	: [{1}Enabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.GREEN, Fore.WHITE, Style.RESET_ALL))

    if len(keychain["linkedin_username"]) > 0 and len(keychain["linkedin_password"]) and args.linkedinner:
        print("{0}   LinkedInner Module	: [{1}Enabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.GREEN, Fore.WHITE, Style.RESET_ALL))
        master_switch.linkedinner = True
    else:
        print("{0}   LinkedInner Module	: [{1}Disabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.RED, Fore.WHITE, Style.RESET_ALL))
        master_switch.linkedinner = False

    if len(keychain["hunterio"]) == 40:
        print("{0}   HunterIO Module	: [{1}Enabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.GREEN, Fore.WHITE, Style.RESET_ALL))
        master_switch.hunterio = True
    else:
        print("{0}   HunterIO Module	: [{1}Disabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.RED, Fore.WHITE, Style.RESET_ALL))
        master_switch.hunterio = False

    if len(keychain["shodan"]) == 32:
        print("{0}   Shodan Module         : [{1}Enabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.GREEN, Fore.WHITE, Style.RESET_ALL))
        master_switch.shodan = True
    else:
        print("{0}   Shodan Module   	 : [{1}Disabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.RED, Fore.WHITE, Style.RESET_ALL))
        master_switch.shodan = False

    if len(keychain["virustotal"]) == 64:
        print(
            "{0}   VirusTotal Module	: [{1}Enabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.GREEN, Fore.WHITE, Style.RESET_ALL))
        master_switch.virustotal = True
    else:
        print(
            "{0}   VirusTotal Module	: [{1}Disabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.RED, Fore.WHITE, Style.RESET_ALL))
        master_switch.virustotal = False

    if len(keychain["censys_id"]) > 0 and len(keychain["censys_secret"]) > 0:
        print("{0}   Censys Module	: [{1}Enabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.GREEN, Fore.WHITE, Style.RESET_ALL))
        master_switch.censys = True
    else:
        print("{0}   Censys Module	: [{1}Disabled{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.RED, Fore.WHITE, Style.RESET_ALL))
        master_switch.censys = False

    if args.expand:
        print("{0}   SubHunter Module	: [{1}Recursive{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.YELLOW, Fore.WHITE, Style.RESET_ALL))
        master_switch.subhunter = True
    else:
        print("{0}   SubHunter Module	: [{1}Active{2}]{3}".format(Fore.WHITE + Style.BRIGHT, Fore.GREEN, Fore.WHITE, Style.RESET_ALL))


# msave Function - Store output in MongoDB [UAT]
def msave(t):
    mtarget = {
        "Address": t.address,
        "ASN": t.asn,
        "Hostnames": t.hname,
        "Apps": t.urls,
        "IPv6": t.ipv6,
        "CIDR": t.cidr
    }
    store_targets.insert_one(mtarget)
    print("Stored")


# cprint Function - Prints Coloured Messages
def cprint(type, msg, reset):
    colorama.init()
    message = {
        "action": Fore.YELLOW,
        "positive": Fore.GREEN + Style.BRIGHT,
        "info": Fore.YELLOW,
        "reset": Style.RESET_ALL,
        "red": Fore.RED,
        "white": Fore.WHITE,
        "green": Fore.GREEN,
        "yellow": Fore.YELLOW
    }
    style = message.get(type.lower())

    if type == "error":
        print("{0}\n[*] Error: {1}".format(Fore.RED + Style.BRIGHT, Style.RESET_ALL + Fore.WHITE + msg))
    else:
        print(style + msg, end="")
    if reset == 1:
        print(Style.RESET_ALL)


# Print Results Function -
def store_results(hostx, output_path):
    results_path = output_path + "/" + hostx.primary_domain

    try:
        os.mkdir(results_path)
    except:
        pass

    emails_filepath = results_path + "/" + 'emails.txt'
    emails_file = open(emails_filepath, 'w')

    g_emails_filepath = results_path + "/" + 'guessed_emails.txt'
    g_emails_file = open(g_emails_filepath, 'w')

    usernames_filepath = results_path + "/" + 'usernames.txt'
    usernames_file = open(usernames_filepath, 'w')

    creds_filepath = results_path + "/" + 'creds.txt'
    creds_file = open(creds_filepath, 'w')

    hashes_filepath = results_path + "/" + 'hashes.txt'
    hashes_file = open(hashes_filepath, 'w')

    subs_filepath = results_path + "/" + 'subdomains.txt'
    subs_file = open(subs_filepath, 'w')

    dns_filepath = results_path + "/" + 'dns_records.txt'
    dns_file = open(dns_filepath, 'w')

    employees_filepath = results_path + "/" + 'employees.csv'
    employees_file = open(employees_filepath, 'w')

    targetips_filepath = results_path + "/" + 'target_ips.csv'
    targetips_file = open(targetips_filepath, 'w')

    buckets_filepath = results_path + "/" + 's3buckets.txt'
    buckets_file = open(buckets_filepath, 'w')

    spoofchecks_filepath = results_path + "/" + 'email_spoof_checks.txt'
    spoofchecks_file = open(spoofchecks_filepath, 'w')

    for email in hostx.emails:
        emails_file.write(email + '\n')

    for email in hostx.emails:
        usernames_file.write(email.split('@', 1)[0] + '\n')

    for cred in hostx.creds:
        creds_file.write(cred + '\n')

    for hash in hostx.hashes:
        hashes_file.write(hash + '\n')

    for sub in hostx.subdomains:
        subs_file.write(sub + '\n')

    for record in hostx.dnsrecords:
        dns_file.write(record + '\n')

    for bucket in hostx.buckets:
        buckets_file.write(bucket + '\n')

    for gemail in hostx.guessed_emails:
        g_emails_file.write(gemail + '\n')

    spoofchecks_file.write("Target : " + hostx.primary_domain + '\n')
    spoofchecks_file.write("SPF : " + str(hostx.spf) + '\n')
    spoofchecks_file.write("DMARC Status : " + hostx.dmarc_status + '\n')
    spoofchecks_file.write("DMARC Record : " + '\n')

    for record in hostx.dmarc:
        spoofchecks_file.write(record + '\n')

    if len(hostx.employees) > 0:
        employees_file.write("\"" + "Email Address" + "\",\"" + "Full Name" + "\",\"" + "First Name" + "\",\"" + "Last Name" + "\",\"" + "LinkedIn Profile URL" + "\"\n")
        for employee in hostx.employees:
            employees_file.write("\"" + employee[0] + "\",\"" + employee[1] + "\",\"" + employee[2] + "\",\"" + employee[3] + "\",\"" + employee[4] + "\"\n")

    # Write Header
    targetips_file.write(
        "\"" + "IP Address" + "\",\"" + "Port/Protocol" + "\",\"" + "ASN" + "\",\"" + "ASN Description" + "\",\"" + "Vulnerabities" + "\",\"" + "Location" + "\"\n")
    for ip in hostx.resolved_ips:
        targetips_file.write("\"" + ip.address + "\",\"" + ", ".join(
            map(str, ip.ports)) + "\",\"" + ip.asn + "\",\"" + ip.asn_name + "\",\"" + ", ".join(
            map(str, ip.vulns)) + "\",\"" + ip.location + "\"\n")


# Print Results Function - Terminal Output
def print_results(count1, stime):
    ttime = round(time() - stime, 2)
    cprint("green", "\n" + "-" * 80, 1)
    print(Fore.YELLOW + "\n[%] Analysed {0} Targets in {1} sec".format(
        Fore.RED + Style.BRIGHT + str(count1.targets) + Style.RESET_ALL + Fore.YELLOW,
        Fore.WHITE + Style.BRIGHT + str(ttime) + Fore.YELLOW))
    print(Fore.WHITE + Style.BRIGHT + "\n[%] Discovered:" + Style.RESET_ALL)
    print(" {0} IPs".format(Fore.RED + Style.BRIGHT + str(count1.ips) + Style.RESET_ALL + Fore.WHITE))
    print(" {0} Open Ports".format(Fore.RED + Style.BRIGHT + str(count1.ports) + Style.RESET_ALL + Fore.WHITE))
    print(" {0} Hostnames".format(Fore.RED + Style.BRIGHT + str(count1.hostnames) + Style.RESET_ALL + Fore.WHITE))
    print(" {0} Subdomains".format(Fore.RED + Style.BRIGHT + str(count1.subdomains) + Style.RESET_ALL + Fore.WHITE))
    print(" {0} Vulnerabities".format(Fore.RED + Style.BRIGHT + str(count1.vulns) + Style.RESET_ALL + Fore.WHITE))
    print(Fore.WHITE + Style.BRIGHT + "\n[%] Intelligence Extracted:" + Style.RESET_ALL)
    print(" {0} Employees' Details".format(
        Fore.RED + Style.BRIGHT + str(count1.employees) + Style.RESET_ALL + Fore.WHITE))
    print(" {0} AWS Buckets Discovered".format(
        Fore.RED + Style.BRIGHT + str(count1.buckets) + Style.RESET_ALL + Fore.WHITE))
    print(" {0} Email Addresses".format(Fore.RED + Style.BRIGHT + str(count1.emails) + Style.RESET_ALL + Fore.WHITE))
    print(" {0} Guessed Emails".format(
        Fore.RED + Style.BRIGHT + str(count1.guessed_emails) + Style.RESET_ALL + Fore.WHITE))
    print(" {0} Credentials".format(Fore.RED + Style.BRIGHT + str(count1.creds) + Style.RESET_ALL + Fore.WHITE))
    print(" {0} Password Hashes".format(Fore.RED + Style.BRIGHT + str(count1.hashes) + Style.RESET_ALL + Fore.WHITE))
    print(" {0} Screenshots {1}".format(Fore.RED + Style.BRIGHT + str(count1.sc) + Style.RESET_ALL + Fore.WHITE,
                                        Style.RESET_ALL))
    cprint("green", "\n" + "-" * 80, 1)


# Main Function
def main(keychain, switch, output_path, count):
    validated_input = []
    targets = []
    target_list = dict()  # Creates a Dict of targets
    if args.target:
        targets.append(args.target)
    else:
        targets = open(args.targets, "rt")  # Read File

    # Add Domain Name Targets
    for line in targets:
        if line == "" or line == "\n":
            continue
        if not add_target_domain(target_list, line, validated_input):
            continue
    # End of For Loop

    # Add IPv4 Targets
    for line in validated_input:
        if not add_target_ip(target_list, line):
            continue
    # End of For Loop

    # Debug Functionality
    if switch.debug is True:
        for k in target_list.keys():
            print("*************************")
            print("[DEBUG] IP Target Added to Dictionary:", k)
            for x in target_list[k].resolved_ips:
                print(">> " + x.address)
            print("*************************")

    # Iterates Through the Target List
    for key in target_list.keys():
        # [B] Target - Domain Name - Execution Flow
        cprint("white", "\n[+] Target Domain: ", 0)
        cprint("red", target_list[key].primary_domain, 1)

        for ip in target_list[key].resolved_ips:
            cprint("white", "	|", 1)
            cprint("white", "  [{0}]".format(ip.address), 1)

        if switch.expand is True:
            subhunter.active(switch, target_list[key], args.wordlist, args.subwordlist, recursive=True)  # Passive
        else:
            subhunter.active(switch, target_list[key], args.wordlist, args.subwordlist,
                             recursive=False)  # Passive Recursive Depth=2

        # HTTP Based
        if switch.stealth is False:
            hosthunter.active(target_list[key], count)  # Active

        # IP Based
        if switch.shodan is True:
            shodan.port_scan(target_list[key], keychain["shodan"], count)  # Passive

        if switch.whois_collector is True and switch.stealth is False:
            whois_collector.wlookup(target_list[key])  # Active

        if switch.censys is True:
            censys.port_scan(target_list[key], keychain["censys_id"], keychain["censys_secret"], count)  # Passive

        # hosthunter.query_api(target_list[key]) # Passive
        hosthunter.org_finder(target_list[key])  # Passive

        buckethunter.passive_query(switch,target_list[key], keychain["grayhatwarfare"])  # Passive

        if switch.expand is True:
            asn_expansion(target_list[key])  # Passive

        # Domain Based
        hosthunter.dnslookup(target_list[key])  # Passive
        urlscanio.query(target_list[key])  # Passive

        if switch.virustotal is True:
            subhunter.passive_query(target_list[key], keychain["virustotal"])  # Passive

        if switch.stealth is not True:
            hosthunter.dnsquery(target_list[key])  # Active
            pass

        if switch.hunterio is True:
            hunterio.query(target_list[key], keychain["hunterio"])

        map_path = dnsdumpster.get_map(target_list[key], output_path)  # Passive

        if (args.screen_capture and not args.stealth):
            screencapture.main(target_list[key], output_path)

        if len(target_list[key].orgName) > 0:
            print("\n {0}|| Organisation Name : {1}".format(Fore.WHITE,
                                                            Fore.YELLOW + target_list[key].orgName + Style.RESET_ALL))

        if switch.linkedinner is True:
            answer2 = input(
                Fore.WHITE + "[" + Fore.RED + Style.BRIGHT + ">" + Style.RESET_ALL + Fore.WHITE + "]" + Fore.WHITE + " Enter Company Name / Company ID: ")
            if answer2.isdigit() and len(answer2) > 0:
                cprint("info", "[i] Searching Linkedin with CompanyID: " + answer2, 1)
                # LinkedInUsername, linkedin_password, company_name, companyid

                linkedinner.get_emails_for_company_name(switch, target_list[key], keychain["linkedin_username"],
                                                        keychain["linkedin_password"], "", answer2)
            elif not answer2.isdigit() and len(answer2) > 0:
                cprint("info", "[i] Searching Linkedin with Company Name: " + answer2, 1)
                linkedinner.get_emails_for_company_name(switch, target_list[key], keychain["linkedin_username"],
                                                        keychain["linkedin_password"], answer2, 0)
            elif len(target_list[key].orgName) > 0:
                cprint("info", "[i] Searching Linkedin with Company Name: " + target_list[key].orgName.replace(",",
                                                                                                                 "").replace(
                    ".", ""), 1)
                answer2 = str(
                    target_list[key].orgName.replace(",", "").replace(".", "").replace(" ", "%20").replace(" LLC",
                                                                                                           "").replace(
                        " LTD", ""))
                linkedinner.get_emails_for_company_name(switch, target_list[key], keychain["linkedin_username"],
                                                        keychain["linkedin_password"], answer2, 0)
            else:
                cprint("error", "Linkedinner module has been disabled. No valid input was detected.", 1)

        if len(target_list[key].subdomains) > 0:
            print(Fore.WHITE + " || Subdomains: " + Fore.YELLOW + str(
                len(target_list[key].subdomains)) + Style.RESET_ALL)
            for i in range(len(target_list[key].subdomains)):
                cprint("info", target_list[key].subdomains[i], 0)
                if i > 50:
                    if len(target_list[key].subdomains) > 50:
                        cprint("info", "\n...", 1)
                    else:
                        cprint("info", "", 1)
                    break
                if i == (len(target_list[key].subdomains)) - 1:
                    cprint("info", "", 1)
                else:
                    cprint("info", ",", 0)

        if len(target_list[key].emails) > 0:
            print(Fore.WHITE + " || Emails: " + Fore.YELLOW + str(len(target_list[key].emails)) + Style.RESET_ALL)
            for i in range(len(target_list[key].emails)):
                cprint("yellow", target_list[key].emails[i], 0)
                if i > 50:
                    if len(target_list[key].emails) > 50:
                        cprint("yellow", "\n...", 1)
                    else:
                        cprint("info", "", 1)
                    break
                if i == (len(target_list[key].emails)) - 1:
                    cprint("info", "", 1)
                else:
                    cprint("info", ",", 0)

        if len(target_list[key].guessed_emails) > 0:
            print(Fore.WHITE + " || Guessed Emails: " + Fore.YELLOW + str(
                len(target_list[key].guessed_emails)) + Style.RESET_ALL)
            for i in range(len(target_list[key].guessed_emails)):
                cprint("yellow", target_list[key].guessed_emails[i], 0)
                if i > 50:
                    if len(target_list[key].guessed_emails) > 50:
                        cprint("yellow", "\n...", 1)
                    else:
                        cprint("info", "", 1)
                    break
                if i == (len(target_list[key].guessed_emails)) - 1:
                    cprint("info", "", 1)
                else:
                    cprint("info", ",", 0)

# WeLeakInfo Code - Left here for future use
#    if len(target_list[key].breaches) > 0:
#        cprint("white", " || WeLeakInfo Data Breaches: ", 1)
#            for email, breach in target_list[key].breaches.items():
#                cprint("yellow", "{0} : {1}".format(email, breach), 1)
#        if len(target_list[key].creds) > 0:
#            cprint("white", " || WeLeakInfo Credentials Discovered: ", 0)
#            cprint("info", "" + str(len(target_list[key].creds)), 1)
#            for i in range(len(target_list[key].creds)):
#                cprint("yellow", target_list[key].creds[i], 1)
#                if i > 8:
#                    if len(target_list[key].creds) > 8:
#                        cprint("yellow", "...", 1)
#                    break
#        if len(target_list[key].hashes) > 0:
#            cprint("white", " || WeLeakInfo Hashes Discovered: ", 0)
#            cprint("info", "" + str(len(target_list[key].hashes)), 1)
#            for i in range(len(target_list[key].hashes)):
#                cprint("yellow", target_list[key].hashes[i], 1)
#                if i > 8:
#                    if len(target_list[key].hashes) > 5:
#                        cprint("yellow", "...", 1)
#                    break
#

        if len(target_list[key].buckets) > 0:
            cprint("white", " || AWS Buckets Discovered: ", 0)
            cprint("info", "" + str(len(target_list[key].buckets)), 1)
            for i in range(len(target_list[key].buckets)):
                cprint("yellow", target_list[key].buckets[i], 1)
                if i > 5:
                    if len(target_list[key].buckets) > 5:
                        cprint("yellow", "\n...", 1)
                    break

        print(" {0}|| DNS Records : {1}".format(Fore.WHITE, Fore.YELLOW + str(len(target_list[key].dnsrecords)) + Style.RESET_ALL))
        for i in range(len(target_list[key].dnsrecords)):
            cprint("yellow", target_list[key].dnsrecords[i], 1)
            if i > 2:
                if len(target_list[key].dnsrecords) > 2:
                    cprint("info", "...", 1)
                break

        if target_list[key].mx is not None:
            cprint("white", " || MX Records	:", 1)
            for dt in target_list[key].mx:
                cprint("info", str(dt.exchange), 1)

        if target_list[key].spf is not None:
            if target_list[key].spf:
                print(
                    " {0}|| SPF	: {1}".format(Fore.WHITE, Fore.GREEN + str(target_list[key].spf) + Style.RESET_ALL))
            else:
                print(" {0}|| SPF	: {1}".format(Fore.WHITE, Fore.RED + Style.BRIGHT + str(
                    target_list[key].spf) + Style.RESET_ALL))

        if len(target_list[key].dmarc) > 0:
            print(
                " {0}|| DMARC : {1}".format(Fore.WHITE, Fore.GREEN + target_list[key].dmarc_status + Style.RESET_ALL))
        else:
            print(" {0}|| DMARC : {1}".format(Fore.WHITE, Fore.RED + Style.BRIGHT + "False" + Style.RESET_ALL))

        print(" {0}|| dnsDumpster Map: {1}".format(Fore.WHITE, Fore.YELLOW + str(map_path) + Style.RESET_ALL))

        if args.screen_capture:
            print(
                Fore.WHITE + " || Screenshots: " + Fore.YELLOW + os.getcwd() + output_path + "/screenshots" + Style.RESET_ALL)

        # Scan each IP pointing to the same domain
        for ip in target_list[key].resolved_ips:
            print("")
            print(Fore.WHITE + " [-] IP Address: " + Style.BRIGHT + Fore.YELLOW + str(ip.address) + Style.RESET_ALL)
            if ip.hostname:
                print(Fore.WHITE + " 	|| Hostname: " + Fore.YELLOW + ','.join(map(str, ip.hostname)) + Style.RESET_ALL)
            if ip.server:
                print(Fore.WHITE + " 	|| Server: " + Fore.YELLOW + ip.server)
            if ip.ports:
                print(Fore.WHITE + " 	|| Ports: " + Fore.YELLOW + '/tcp, '.join(map(str, ip.ports)) + "/tcp" + Style.RESET_ALL)
            if ip.vulns:
                print(Fore.WHITE + " 	|| Possible Vulnerabities: " + Fore.YELLOW + ','.join(map(str, ip.vulns)) + Style.RESET_ALL)
            if ip.location:
                print(Fore.WHITE + " 	|| Location: " + Fore.YELLOW + ip.location + Style.RESET_ALL)
            print(Fore.WHITE + " 	|| ASN: " + Fore.YELLOW + ip.asn + Style.RESET_ALL)
            if ip.asn_name:
                print(Fore.WHITE + " 	|| ASN Name: " + Fore.YELLOW + str(ip.asn_name) + Style.RESET_ALL)
            print(Fore.WHITE + " 	|| CIDR: " + Fore.YELLOW + ip.cidr + Style.RESET_ALL)
            print("")
            if ip.ports:
                count.ports += len(ip.ports)
            if ip.vulns:
                count.vulns += len(ip.vulns)

        # Update Counters
        count.ips += len(target_list[key].resolved_ips)
        count.targets += 1
        count.subdomains += len(target_list[key].subdomains)
        count.employees += len(target_list[key].employees)
        count.emails += len(target_list[key].emails)
        count.guessed_emails += len(target_list[key].guessed_emails)
        count.creds += len(target_list[key].creds)
        count.hashes += len(target_list[key].hashes)
        count.buckets += len(target_list[key].buckets)

        store_results(target_list[key], output_path)


# Capture SIGINT
def sig_handler(signal, frame):
    cprint("info", "\n\n[i] Shutting down AttackSurfaceMapper. . .\n", 1)  # Success Msg
    try:
        signal.pause()
    except:
        pass
    cprint("info", "\n[i] Bye, bye!\n", 1)  # Success Msg
    sys.exit(0)

if __name__ == "__main__":
    signal.signal(signal.SIGINT, sig_handler)  # Signal Listener
    now = datetime.now()
    output_path = 'asm_run_' + str(datetime.now().strftime("%d.%m.%y_%H-%M-%S"))
    sw1 = MasterSwitch()
    keychain = dict()
    c1 = Counter()
    start_time = time()  # Start Counter

    output_path = init_checks(sw1, output_path)  # Initialisation Checks
    showbanner()  # Banner
    keyloader(keychain, sw1)  # Key Loader

    cprint("info", "\n\n[i] AttackSurfaceMapper is running. . .\n", 1)  # Success Msg

    if not os.path.exists(output_path):
        os.mkdir(output_path)

    main(keychain, sw1, output_path, c1)

    print_results(c1, start_time)  # Print terminal output

    exit()
